/**
 * 
 */
package spitfire.ksim.network;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Adam
 *
 */
public class KNetwork {

	private static final int INFINITY = Integer.MAX_VALUE;
	
	private Map<KNetAddress, KNetworkModule> addrModuleMap = new ConcurrentHashMap<KNetAddress, KNetworkModule>();
	
	public synchronized KNetAddress register(KNetworkModule module) {
		KNetAddress addr = KNetAddress.getNextNetAddr();
		addrModuleMap.put(addr, module);
		return addr;
	}

	public synchronized void deregister(KNetworkModule module) {
		addrModuleMap.remove(module.getNetAddr());
	}
	
	public synchronized void deregister(KNetAddress netAddr) {
		addrModuleMap.remove(netAddr);
	}
	
	public Collection<KNetworkModule> getNetModules() {
		return addrModuleMap.values();
	}
	
	public void transmit(KPacket packet) {
		// TODO optimize the shortest path algorithm (djikastra)
		int[][] adjacencyClosure = calAdjacencyClosure();
		
		KNetAddress src = packet.getSrcAddr();
		KNetAddress dst = packet.getDstAddr();
		if (packet.getDstAddr().equals(KNetAddress.BROADCAST_ADDR)) {
			// Broadcast packet
			for (KNetworkModule host : addrModuleMap.values()) {
				if (adjacencyClosure[src.toInt()][host.getNetAddr().toInt()] != INFINITY) {
					host.recv(packet);
				}
			}
		} else if (!addrModuleMap.containsKey(packet.getDstAddr())) {
			// destination host doesn't exist
			// Do nothing
		} else {
			// destination host exists
			if (adjacencyClosure[src.toInt()][dst.toInt()] != INFINITY) {
				KNetworkModule dstHost = addrModuleMap.get(dst);
				dstHost.recv(packet);
			}
		}
	}

	private synchronized int[][] calAdjacencyClosure() {
		int size = KNetAddress.getAddrToIntMax();
		// generate new matrix
		int[][] mat = new int[size][];
		for (int i = 0; i < size; i++) {
			mat[i] = new int[size];
		}
		// calculate adjacency
		for (KNetworkModule src : addrModuleMap.values()) {
			for (KNetworkModule dst : addrModuleMap.values()) {
				int srcIdx = src.getNetAddr().toInt();
				int dstIdx = dst.getNetAddr().toInt();
				if (srcIdx == dstIdx) {
					// 0 denotes itself
					mat[srcIdx][dstIdx] = 0;
				} else {
					// 1 denotes adjacent and -1 denotes not adjacent
					mat[srcIdx][dstIdx] = src.adjacentWith(dst) ? 1 : INFINITY;
				}
			}
		}	
		// calculate closure
		for (int k = 0; k < size; k++) {
			for (int i = 0; i < size; i++) {
				for (int j = 0; j < size; j++) {
					int dik = mat[i][k];
					int dkj = mat[k][j];
					if (dik + dkj > 0 && dik + dkj <= mat[i][j]) {
						mat[i][j] = dik + dkj;
					}
				}
			}
		}
		return mat;
	}

//	private void updateClosure() {
//		// copy adjacency matrix to closure
//		int size = adjacency.length;
//		adjacencyClosure = new int[size][];
//		for (int i = 0; i < size; i++) {
//			adjacencyClosure[i] = adjacency[i].clone();
//		}
//		// calculate closure
//		for (int k = 0; k < size; k++) {
//			for (int i = 0; i < size; i++) {
//				for (int j = 0; j < size; j++) {
//					int dik = adjacencyClosure[i][k];
//					int dkj = adjacencyClosure[k][j];
//					if (dik + dkj > 0 && dik + dkj <= adjacencyClosure[i][j]) {
//						adjacencyClosure[i][j] = dik + dkj;
//					}
//				}
//			}
//		}
//	}

	public void destroy() {
		addrModuleMap.clear();
		KPacket.resetID();
		KNetAddress.resetID();
	}
	
//	public void test() {
////		updateAdjacency();
////		updateClosure();
//		System.out.println(Arrays.deepToString(calAdjacencyClosure()));
//	}
}
